import sys
import time
import random

from PyQt5.QtCore import Qt, QTimer, QObject, QPoint
from PyQt5.QtGui import QImage, QPixmap, QIcon, QCursor
from PyQt5.QtWidgets import *
from typing import List

from utils import *
from conf import *


class PetWidget(QWidget):
    def __init__(self, parent=None, curr_pet_name='', pets=()):
        """
        宠物组件
        :param parent: 父窗口
        :param curr_pet_name: 当前宠物名称
        :param pets: 全部宠物列表
        """
        super(PetWidget, self).__init__(parent, flags=Qt.WindowFlags())
        self.curr_pet_name = ''
        self.pet_conf = PetConfig()
        self.image = None
        self.label = QLabel(self)
        # 鼠标拖拽初始属性
        self.is_follow_mouse = False
        self.mouse_drag_pos = self.pos()
        # 是否在执行动画
        self.is_run_act = False

        self._init_widget()
        self.init_conf(curr_pet_name if curr_pet_name else pets[0])
        self._set_menu(pets)
        self._set_tray()
        self.show()

        # 每5秒执行随机执行一个动作
        self.timer = QTimer()
        self.timer.timeout.connect(self.random_act)
        self.timer.start(self.pet_conf.refresh)

    def mousePressEvent(self, event):
        """
        鼠标点击事件
        :param event: 事件
        :return:
        """
        if event.button() == Qt.RightButton:
            # 打开右键菜单
            self.setContextMenuPolicy(Qt.CustomContextMenu)
            self.customContextMenuRequested.connect(self._show_right_menu)
        if event.button() == Qt.LeftButton:
            # 左键绑定拖拽
            self.is_follow_mouse = True
            self.mouse_drag_pos = event.globalPos() - self.pos()
            event.accept()
            self.setCursor(QCursor(Qt.ArrowCursor))

    def mouseMoveEvent(self, event):
        """
        鼠标移动事件, 左键且绑定跟随, 移动窗体
        :param event:
        :return:
        """
        if Qt.LeftButton and self.is_follow_mouse:
            self.move(event.globalPos() - self.mouse_drag_pos)
            event.accept()

    def mouseReleaseEvent(self, event):
        """
        松开鼠标操作
        :param event:
        :return:
        """
        self.is_follow_mouse = False
        self.setCursor(QCursor(Qt.ArrowCursor))

    def _init_widget(self) -> None:
        """
        初始化窗体, 无边框半透明窗口
        :return:
        """
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.SubWindow)
        self.setAutoFillBackground(False)
        self.setAttribute(Qt.WA_TranslucentBackground, True)
        self.repaint()
        # 是否跟随鼠标
        self.is_follow_mouse = False
        self.mouse_drag_pos = self.pos()
        self.resize(self.pet_conf.size, self.pet_conf.size)

    def _init_img(self, img: QImage) -> None:
        """
        初始化窗体图片
        :param img:
        :return:
        """
        self.set_img(img)
        self.resize(self.pet_conf.size, self.pet_conf.size)
        self.show()

    def _set_menu(self, pets=()):
        """
        初始化菜单
        """
        menu = QMenu(self)

        # 切换角色子菜单
        change_menu = QMenu(menu)
        change_menu.setTitle('切换角色')
        change_acts = [_build_change_act(name, change_menu, self._change_pet) for name in pets]
        change_menu.addActions(change_acts)
        menu.addMenu(change_menu)

        # 退出动作
        quit_act = QAction('退出', menu)
        quit_act.triggered.connect(self.quit)
        menu.addAction(quit_act)
        self.menu = menu

    def _show_right_menu(self):
        """
        展示右键菜单
        :return:
        """
        # 光标位置弹出菜单
        self.menu.popup(QCursor.pos())

    def _change_pet(self, pet_name: str) -> None:
        """
        改变宠物
        :param pet_name: 宠物名称
        :return:
        """
        self.init_conf(pet_name)
        self.set_img(self.image)
        self.repaint()

    def init_conf(self, pet_name: str) -> None:
        """
        初始化宠物窗口配置
        :param pet_name: 宠物名称
        :return:
        """
        self.curr_pet_name = pet_name
        pic_dict = _load_all_pic(pet_name)
        self.pet_conf = PetConfig.init_config(self.curr_pet_name, pic_dict)
        self.set_img(list(pic_dict.values())[0])

    def _set_tray(self) -> None:
        """
        设置最小化托盘
        :return:
        """
        tray = QSystemTrayIcon(self)
        tray.setIcon(QIcon('res/icon.png'))
        tray.setContextMenu(self.menu)
        tray.show()

    def set_img(self, img: QImage) -> None:
        """
        为窗体设置图片
        :param img: 图片
        :return:
        """
        self.label.setPixmap(QPixmap.fromImage(img))
        self.image = img

    def random_act(self) -> None:
        """
        随机执行动作
        :return:
        """
        # 如果菜单已打开, 则关闭菜单
        if self.menu.isEnabled():
            self.menu.close()

        if self.is_run_act:
            return

        self.is_run_act = True
        # 选取随机动作执行
        acts = random.choice(self.pet_conf.random_act)
        self._run_acts(acts)

    def _run_acts(self, acts: List[Act]) -> None:
        """
        执行动画, 将一个动作相关的图片循环展示
        :param acts: 一组关联动作
        :return:
        """
        for act in acts:
            self._run_act(act)
        self.is_run_act = False

    def _run_act(self, act: Act) -> None:
        """
        加载图片执行移动
        :param act: 动作
        :return:
        """
        for i in range(act.act_num):
            for img in act.images:
                self.set_img(img)
                time.sleep(act.frame_refresh)
                if act.need_move:
                    self._move(self.pos(), act)
                else:
                    self._static_act(self.pos())
                self.repaint()

    def _static_act(self, pos: QPoint) -> None:
        """
        静态动作判断位置
        :param pos: 位置
        :return:
        """
        screen_geo = QDesktopWidget().screenGeometry()
        screen_width = screen_geo.width()
        screen_height = screen_geo.height()
        border = self.pet_conf.size
        new_x = pos.x()
        new_y = pos.y()
        if pos.x() < border:
            new_x = screen_width - border
        elif pos.x() > screen_width - border:
            new_x = border
        if pos.y() < border:
            new_y = screen_height - border
        elif pos.y() > screen_height - border:
            new_y = border
        self.move(new_x, new_y)

    def _move(self, pos: QPoint, act: QAction) -> None:
        """
        移动动作
        :param pos: 当前位置
        :param act: 动作
        :return
        """
        screen_geo = QDesktopWidget().screenGeometry()
        screen_width = screen_geo.width()
        screen_height = screen_geo.height()
        border = self.pet_conf.size
        if act.direction == 'right':
            new_x = pos.x() + act.frame_move
            if new_x < screen_width - border:
                self.move(new_x, pos.y())
            else:
                self.move(border, pos.y())
        if act.direction == 'left':
            new_x = pos.x() - act.frame_move
            if new_x > border:
                self.move(new_x, pos.y())
            else:
                self.move(screen_width - border, pos.y())
        if act.direction == 'up':
            new_y = pos.y() - act.frame_move
            if new_y > border:
                self.move(pos.x(), new_y)
            else:
                self.move(pos.x(), screen_height - border)
        if act.direction == 'down':
            new_y = pos.y() + act.frame_move
            if new_y < screen_height - border:
                self.move(pos.x(), new_y)
            else:
                self.move(pos.x(), border)
        if act.direction == 'left_down':
            new_y = pos.y() + act.frame_move
            new_x = pos.x() - act.frame_move
            if new_x > border and new_y < screen_height - border:
                self.move(new_x, new_y)
            else:
                self.move(screen_width - border, border)

    def quit(self) -> None:
        """
        关闭窗口, 系统退出
        :return:
        """
        self.close()
        sys.exit()


def _load_all_pic(pet_name: str) -> dict:
    """
    加载宠物所有动作图片
    :param pet_name: 宠物名称
    :return: {动作编码: 动作图片}
    """
    img_dir = 'res/role/{}/action/'.format(pet_name)
    images = os.listdir(img_dir)
    return {image.split('.')[0]: _get_q_img(img_dir + image) for image in images}


def _build_change_act(pet_name: str, parent: QObject, act_func) -> QAction:
    """
    构建改变菜单动作
    :param pet_name: 菜单动作名称
    :param parent 父级菜单
    :param act_func: 菜单动作函数
    :return:
    """
    act = QAction(pet_name, parent)
    act.triggered.connect(lambda: act_func(pet_name))
    return act


def _get_q_img(img_path: str) -> QImage:
    """
    将图片路径加载为 QImage
    :param img_path: 图片路径
    :return: QImage
    """
    image = QImage()
    image.load(img_path)
    return image
